package com.dommettj.rawfix.message;

import com.dommettj.rawfix.settings.Settings;
import com.dommettj.rawfix.utils.Clock;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FixMessageGenerator implements MessageGenerator {

    private static final int[] HEADER_TAGS = new int[] {9,35,49,56,115,128,90,91,34,50,142,57,143,116,144,129,145,43,97,52,122,212,213,347,369,370,627,628,629,630};
    private static final char TAG_DELIM = '\u0001';
    private static final String EMPTY_BODY = "";
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HH:mm:ss.SSS");
    private final Clock clock;
    private final String fixVersion;
    private String senderCompId;
    private String targetCompId;
    private long messageSequenceNumber;

    public FixMessageGenerator(final Clock clock, final String fixVersion, final String senderCompId, final String targetCompId) {
        this.clock = clock;
        this.fixVersion = fixVersion;
        this.senderCompId = senderCompId;
        this.targetCompId = targetCompId;
        this.messageSequenceNumber = 0;
    }

    @Override
    public String createHeartbeatMessage() {

        incrementMessageSequence();
        return constructMessage(MessageType.HEARTBEAT, EMPTY_BODY);
    }

    @Override
    public String createLogonMessage() {

        incrementMessageSequence();
        return constructMessage(MessageType.LOGON, "98=0\u0001108=30\u0001141=Y\u0001");
    }

    @Override
    public String createLogoffMessage() {

        incrementMessageSequence();
        return constructMessage(MessageType.LOGOFF, EMPTY_BODY);
    }

    @Override
    public String constructMessage(String messageBody) throws MalformedMessageException {

        try {
            final String trimmedMessage = messageBody.trim();
            final char tagDelimiter = MessageUtils.getTagDelimiter(trimmedMessage);
            final MessageType messageType = MessageUtils.getMessageType(trimmedMessage, tagDelimiter);

            final String escapedDelimiter = MessageUtils.getEscapedDelimiter(tagDelimiter);
            final String messageWithoutHeaderOrChecksum = removeHeaderAndChecksumTags(trimmedMessage, escapedDelimiter);
            final String messageWithCorrectDelimiter = messageWithoutHeaderOrChecksum.replaceAll(escapedDelimiter, "\u0001");

            incrementMessageSequence();
            return constructMessage(messageType, messageWithCorrectDelimiter);

        } catch (UnknownMessageTypeException e) {
            throw new MalformedMessageException(messageBody);
        }
    }

    private String removeHeaderAndChecksumTags(final String message, final String delimiter) {

        String currentMessage = message;

        final Pattern firstTagPattern = Pattern.compile("^8=(.*?)(" + delimiter + ")(.*)");
        final Matcher firstTagMatcher = firstTagPattern.matcher(currentMessage);
        if (firstTagMatcher.matches()) {
            currentMessage = firstTagMatcher.group(2) + firstTagMatcher.group(3);
        }

        for(int HEADER_TAG : HEADER_TAGS) {

            final Pattern tagPattern = Pattern.compile("(.*)" + delimiter + HEADER_TAG + "=(.*?)(" + delimiter + ")(.*)");
            final Matcher tagMatcher = tagPattern.matcher(currentMessage);
            if (tagMatcher.matches()) {
                currentMessage = tagMatcher.group(1) + tagMatcher.group(3) + tagMatcher.group(4);
            }
        }

        final Pattern checksum = Pattern.compile("(" + delimiter + ")?(.*)"  + 10 + "=(...)(" + delimiter + ")?$");
        final Matcher tagMatcher = checksum.matcher(currentMessage);
        if (tagMatcher.matches()) {
            currentMessage = tagMatcher.group(2);
        }

        return currentMessage;
    }


    @Override
    public void resetMessageSequence() {
        messageSequenceNumber = 0;
    }

    @Override
    public void notifySettingsUpdated(Settings settings) {
        this.senderCompId = settings.loadSenderCompId();
        this.targetCompId = settings.loadTargetCompId();
    }

    private String constructMessage(final MessageType messageType, final String messageBody) {

        final String headerAndBody = generateHeaderFields(messageType) + messageBody;
        final int msgLength = headerAndBody.length();
        final StringBuilder sb = new StringBuilder();
        sb.append("8=").append(fixVersion).append(TAG_DELIM);
        sb.append("9=").append(msgLength).append(TAG_DELIM);
        sb.append(headerAndBody);

        final int checksum = calculateChecksum(sb.toString());
        sb.append("10=").append(String.format("%03d", checksum)).append(TAG_DELIM);
        return sb.toString();
    }

    private String generateHeaderFields(final MessageType messageType) {

        final StringBuilder sb = new StringBuilder();
        sb.append("35=").append(messageType.getTypeString()).append(TAG_DELIM);
        sb.append("34=").append(messageSequenceNumber).append(TAG_DELIM);
        sb.append("49=").append(senderCompId).append(TAG_DELIM);
        sb.append("56=").append(targetCompId).append(TAG_DELIM);
        sb.append("52=").append(sdf.format(new Date(clock.currentTimeMillis()))).append(TAG_DELIM);
        return sb.toString();
    }

    private int calculateChecksum(final String message) {

        char[] messageChars = message.toCharArray();
        int checksum = 0;
        for(char c : messageChars) {
            checksum += (c == TAG_DELIM ? 1 : (int)c);
        }
        checksum = checksum%256;
        return checksum;
    }

    private void incrementMessageSequence() {
        this.messageSequenceNumber++;
    }
}
